﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using SimpleIR;
using System.Diagnostics.Contracts;

namespace RgnAnalyzer.Domain
{
	/// <summary>
	/// This class is used to represent delegate object values (just maps of fields to types).
	/// </summary>
	internal sealed class ADelegate : AObject
	{
		/// <summary>
		/// True if we know there can be at most one concrete object assocaited with this abstract object.
		/// </summary>
		private readonly HashSet<IRInvokeIdentity> m_invsigs;

		private ADelegate(int address, IRLambdaType tt, bool uniqueloc, int[] identity, EscapeSiteInfo[] esclocinfo, List<UseModInfo> uml, APtrValueMap fvalues, HashSet<IRInvokeIdentity> sigs)
			: base(address, tt, uniqueloc, identity, esclocinfo, uml, fvalues)
		{
			Contract.Requires(sigs != null);

			this.m_invsigs = sigs; 
		}

		public override string ToString()
		{
			StringBuilder sb = new StringBuilder("funcs={");
			bool first = true;
			foreach(IRInvokeIdentity iiv in this.m_invsigs)
			{
				if(!first)
					sb.Append(", ");
				first = false;
				sb.Append(iiv.ToString());
			}
			sb.Append("}");

			return base.ToString() + " with " + sb.ToString();
		}

		internal override ALocation CopyLocation()
		{
			int[] ida = (this.m_identities == EmptyIdentity ? EmptyIdentity : (int[])this.m_identities.Clone());
			EscapeSiteInfo[] fli = (this.m_escapelines == EmptyAllocLines ? ALocation.EmptyAllocLines : (EscapeSiteInfo[])this.m_escapelines.Clone());
			List<UseModInfo> umv = new List<UseModInfo>(this.m_uml);

			return new ADelegate(this.m_locationaddress, (IRLambdaType)this.m_loctype, this.m_uniquelocation, ida, fli, umv, this.m_ptrinfo, new HashSet<IRInvokeIdentity>(this.m_invsigs));
		}

		internal override AObject SimulateObjectClone(List<ALocation> memory, int line)
		{
			int newaddress = memory.Count;
			EscapeSiteInfo[] escinfo = new EscapeSiteInfo[] { new EscapeSiteInfo(line, line, line) };
			APtrValueMap ppa = this.m_ptrinfo.SetAllAsInjectiveAsNeededForUniqueLocation(this.LocationType);

			ADelegate resobj = new ADelegate(newaddress, (IRLambdaType)this.m_loctype, true, EmptyIdentity, escinfo, new List<UseModInfo>(), ppa, new HashSet<IRInvokeIdentity>(this.m_invsigs));
			UseModInfo.MarkUMIAtObjectAllocation((IRClassType)resobj.m_loctype, resobj.m_uml, line);

			return resobj;
		}

		/// <summary>
		/// Given a type create the default value for it.
		/// </summary>
		internal static ADelegate AllocateDelegateOfType(List<ALocation> memory, IRLambdaType tt, APtrValueMap pval, IRInvokeIdentity sig, int line)
		{
			int address = memory.Count;

			EscapeSiteInfo[] escinfo = new EscapeSiteInfo[] { new EscapeSiteInfo(line, line, line) };

			HashSet<IRInvokeIdentity> sigs = new HashSet<IRInvokeIdentity>();
			sigs.Add(sig);

			ADelegate res = new ADelegate(address, tt, true, ALocation.EmptyIdentity, escinfo, new List<UseModInfo>(), APtrValueMap.MakeInitalPtrValueMapComposite(tt.TypeDescription.AllTerminalAccessPaths), sigs);
			UseModInfo.MarkUMIAtObjectAllocation((IRClassType)res.m_loctype, res.m_uml, line);

			IRMemberField trgtf = tt.TypeDescription.AllVisibleFields.First((IRMemberField mf) => mf.FieldName.Equals("target"));
			IRBasicAccessPath trgtp = GlobalAnalysisState.TProg.GetAccessPathForSingleField(trgtf);
			res.DoGeneralWriteToLocation(trgtp, pval, true, line);

			memory.Add(res);
			return res;
		}

		protected override bool EqualALocationSubclass(ALocation ol)
		{
			if(!(ol is ADelegate))
				return false;

			if(!this.m_invsigs.SetEquals(((ADelegate)ol).m_invsigs))
				return false;

			return base.EqualALocationSubclass(ol);
		}

		protected override ALocation JoinLocationsFromDifferentContextsSubclass(ALocation ol, int[] residentities, EscapeSiteInfo[] resfreshlocs, List<UseModInfo> resuml)
		{
			Contract.Assert(ol is ADelegate);

			HashSet<IRInvokeIdentity> sigs = new HashSet<IRInvokeIdentity>();
			sigs.UnionWith(this.m_invsigs);
			sigs.UnionWith(((ADelegate)ol).m_invsigs);

			bool stillunique = (this.m_uniquelocation & ((ADelegate)ol).m_uniquelocation);

			APtrValueMap ppa = APtrValueMap.MergeAPtrMaps(this.m_ptrinfo, ((ADelegate)ol).m_ptrinfo, true, stillunique);
			return new ADelegate(this.m_locationaddress, (IRLambdaType)this.m_loctype, stillunique, residentities, resfreshlocs, resuml, ppa, sigs);
		}

		protected override ALocation JoinLocationSetFromSameContextSubclass(List<ALocation> lset, int resaddress, int[] residentities, EscapeSiteInfo[] resfreshlocs, List<UseModInfo> resuml, Dictionary<int, int> addrReMap)
		{
			Nullable<APtrValueMap> acclm = null;
			HashSet<IRInvokeIdentity> sigs = new HashSet<IRInvokeIdentity>();

			foreach(ALocation ll in lset)
			{
				Contract.Assert(ll is ADelegate);
				
				sigs.UnionWith(((ADelegate)ll).m_invsigs);

				if(!acclm.HasValue)
					acclm = ((ADelegate)ll).m_ptrinfo;
				else
					acclm = APtrValueMap.MergeAPtrMaps(acclm.Value, ((ADelegate)ll).m_ptrinfo, false, false);	
			}

			acclm = acclm.Value.UpdatedAddresses_MayMergeButNoNewInj(addrReMap, false);
			return new ADelegate(resaddress, (IRLambdaType)this.m_loctype, false, residentities, resfreshlocs, resuml, acclm.Value, sigs);
		}

		/// <summary>
		/// Return the invocation associated with this delegate object.
		/// </summary>
		internal HashSet<IRInvokeIdentity> InvocationTargets
		{ get { return this.m_invsigs; } }
	}
}
